package com.sywl.ytcx.util;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.builder.ExcelReaderBuilder;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.write.handler.WriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * EasyExcel 二次封装工具类，支持导出/导入、单/多 Sheet、自定义样式等扩展功能
 * 提供了常用的Excel操作方法，简化了Excel文件的读写操作
 */
public class ExcelUtil {

    // 定义响应的内容类型，表示Excel文件格式
    private static final String CONTENT_TYPE =
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
    // 定义字符编码格式
    private static final String CHARSET_UTF8 = "utf-8";

    /**
     * 准备HTTP响应头信息，设置文件下载相关参数
     * @param response HTTP响应对象
     * @param fileName 文件名
     */
    private static void prepareResponse(HttpServletResponse response, String fileName) {
        try {
            // 设置响应内容类型为Excel格式
            response.setContentType(CONTENT_TYPE);
            // 设置字符编码
            response.setCharacterEncoding(CHARSET_UTF8);
            // 对文件名进行URL编码，防止中文乱码
            String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
            // 设置响应头，指定文件以附件形式下载，文件名为编码后的名称
            response.setHeader("Content-Disposition", "attachment; filename=" + encoded + ".xlsx");
        } catch (IOException e) {
            // 如果编码过程中发生异常，则抛出运行时异常
            throw new RuntimeException("设置下载响应头失败", e);
        }
    }

    /**
     * 导出单个 Sheet 的Excel文件
     * @param response HTTP响应对象，用于输出Excel文件
     * @param clazz 数据实体类，用于映射Excel表头
     * @param data 需要导出的数据列表
     * @param fileName 导出的Excel文件名
     * @param sheetName Excel工作表名称
     * @param <T> 泛型参数，表示数据实体类型
     * @throws IOException 当文件写入或网络传输发生异常时抛出
     */
    public static <T> void writeSingleSheet(HttpServletResponse response,
                                            Class<T> clazz,
                                            List<T> data,
                                            String fileName,
                                            String sheetName) throws IOException {
        // 准备响应头信息
        prepareResponse(response, fileName);
        // 使用EasyExcel写入数据到响应输出流
        // 1. 创建Excel写入器，指定输出流和数据类型
        // 2. 指定工作表名称
        // 3. 执行写入操作
        EasyExcel.write(response.getOutputStream(), clazz)
                .sheet(sheetName)
                .doWrite(data);
    }

    /**
     * 导出包含多个 Sheet 的Excel文件
     * @param response HTTP响应对象，用于输出Excel文件
     * @param clazz 数据实体类，用于映射Excel表头
     * @param dataList 多个Sheet的数据列表，每个元素代表一个Sheet的数据
     * @param fileName 导出的Excel文件名
     * @param sheetNames 各个工作表的名称列表，与dataList一一对应
     * @param <T> 泛型参数，表示数据实体类型
     * @throws IOException 当文件写入或网络传输发生异常时抛出
     */
    public static <T> void writeMultipleSheets(HttpServletResponse response,
                                               Class<T> clazz,
                                               List<List<T>> dataList,
                                               String fileName,
                                               List<String> sheetNames) throws IOException {
        // 准备响应头信息
        prepareResponse(response, fileName);
        // 使用try-with-resources确保ExcelWriter正确关闭
        try (ExcelWriter writer = EasyExcel.write(response.getOutputStream(), clazz).build()) {
            // 遍历数据列表，为每个数据集创建一个Sheet
            for (int i = 0; i < dataList.size(); i++) {
                // 创建工作表对象，指定索引和名称
                WriteSheet sheet = EasyExcel.writerSheet(i, sheetNames.get(i)).build();
                // 将对应的数据写入到指定的工作表中
                writer.write(dataList.get(i), sheet);
            }
        }
    }

    /**
     * 使用模板导出Excel文件
     * @param response HTTP响应对象，用于输出Excel文件
     * @param clazz 数据实体类，用于映射Excel表头
     * @param templatePath 模板文件路径
     * @param data 需要导出的数据列表
     * @param fileName 导出的Excel文件名
     * @param sheetName Excel工作表名称
     * @param <T> 泛型参数，表示数据实体类型
     * @throws IOException 当文件写入或网络传输发生异常时抛出
     */
    public static <T> void writeWithTemplate(HttpServletResponse response,
                                             Class<T> clazz,
                                             String templatePath,
                                             List<T> data,
                                             String fileName,
                                             String sheetName) throws IOException {
        // 准备响应头信息
        prepareResponse(response, fileName);
        // 使用EasyExcel结合模板写入数据
        // 1. 创建Excel写入器，指定输出流和数据类型
        // 2. 指定使用的模板文件
        // 3. 指定工作表名称
        // 4. 执行写入操作
        EasyExcel.write(response.getOutputStream(), clazz)
                .withTemplate(templatePath)
                .sheet(sheetName)
                .doWrite(data);
    }

    /**
     * 使用自定义处理器导出Excel文件（可设置单元格样式、合并单元格等）
     * @param response HTTP响应对象，用于输出Excel文件
     * @param clazz 数据实体类，用于映射Excel表头
     * @param data 需要导出的数据列表
     * @param fileName 导出的Excel文件名
     * @param sheetName Excel工作表名称
     * @param handler 自定义写入处理器，用于设置样式等
     * @param <T> 泛型参数，表示数据实体类型
     * @throws IOException 当文件写入或网络传输发生异常时抛出
     */
    public static <T> void writeWithHandler(HttpServletResponse response,
                                            Class<T> clazz,
                                            List<T> data,
                                            String fileName,
                                            String sheetName,
                                            WriteHandler handler) throws IOException {
        // 准备响应头信息
        prepareResponse(response, fileName);
        // 使用EasyExcel结合自定义处理器写入数据
        // 1. 创建Excel写入器，指定输出流和数据类型
        // 2. 指定工作表名称
        // 3. 注册自定义处理器
        // 4. 执行写入操作
        EasyExcel.write(response.getOutputStream(), clazz)
                .sheet(sheetName)
                .registerWriteHandler(handler)
                .doWrite(data);
    }

    /**
     * 读取Excel文件并监听每行数据
     * @param inputStream Excel文件输入流
     * @param clazz 数据实体类，用于映射Excel表头
     * @param listener 数据读取监听器，处理每一行读取到的数据
     * @param <T> 泛型参数，表示数据实体类型
     */
    public static <T> void readExcel(InputStream inputStream,
                                     Class<T> clazz,
                                     PageReadListener<T> listener) {
        // 创建Excel读取构建器，指定输入流、数据类型和监听器
        ExcelReaderBuilder builder = EasyExcel.read(inputStream, clazz, listener);
        // 指定读取第一个工作表并执行读取操作
        builder.sheet().doRead();
    }
}
